﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Brain
{
    public static class ModelUtil
    {
        public static BrainModel GenerateCylinderMesh(float height, float diameter, int tessellation)
        {
            if (tessellation < 3)
            {
                throw new ArgumentOutOfRangeException("tesselation");
            }

            height /= 2;

            BrainModel model = new BrainModel();

            float radius = diameter / 2;

            List<VertexPositionNormal> vertices = new List<VertexPositionNormal>();
            List<short> indices = new List<short>();
            // Create a ring of triangles around the outside of the cylinder.

            for (short i = 0; i < tessellation; i++)
            {
                Vector3 normal = GetCircleVector(i, tessellation);

                vertices.Add(new VertexPositionNormal(normal * radius + Vector3.Up * height, normal));
                vertices.Add(new VertexPositionNormal(normal * radius + Vector3.Down * height, normal));

                indices.Add((short)(i * 2));
                indices.Add((short)(i * 2 + 1));
                indices.Add((short)((i * 2 + 2) % (tessellation * 2)));

                indices.Add((short)(i * 2 + 1));
                indices.Add((short)((i * 2 + 3) % (tessellation * 2)));
                indices.Add((short)((i * 2 + 2) % (tessellation * 2)));
            }

            // Create flat triangle fan caps to seal the top and bottom.
            CreateCap(indices, vertices, tessellation, height, radius, Vector3.Up);
            CreateCap(indices, vertices, tessellation, height, radius, Vector3.Down);
                        

            VertexBuffer buffer = new VertexBuffer(Engine.Instance.GraphicsDevice, typeof(VertexPositionNormal),
                vertices.Count, BufferUsage.None);
            buffer.SetData(vertices.ToArray());

            IndexBuffer IBuffer = new IndexBuffer(Engine.Instance.GraphicsDevice, IndexElementSize.SixteenBits,
                indices.Count, BufferUsage.None);
            IBuffer.SetData(indices.ToArray());

            BrainModelMesh mesh = new BrainModelMesh();
            mesh.VertexBuffer = buffer;
            mesh.IndexBuffer = IBuffer;
            mesh.NumVertices = vertices.Count;
            mesh.PrimitiveCount = indices.Count;
            mesh.StartIndex = 0;
            mesh.VertexOffset = 0;

            model.Meshes = new BrainModelMesh[1];
            model.Meshes[0] = mesh;

            return model;
        }

        /// <summary>
        /// Helper method computes a point on a circle.
        /// </summary>
        private static Vector3 GetCircleVector(int i, int tessellation)
        {
            float angle = i * MathHelper.TwoPi / tessellation;

            float dx = (float)Math.Cos(angle);
            float dz = (float)Math.Sin(angle);

            return new Vector3(dx, 0, dz);
        }

        private static void CreateCap(List<short> indices, List<VertexPositionNormal> vertices, int tessellation, float height, float radius, Vector3 normal)
        {
            int CurrentVertex = vertices.Count;
            // Create cap indices.
            for (int i = 0; i < tessellation - 2; i++)
            {
                if (normal.Y > 0)
                {
                    indices.Add((short)CurrentVertex);
                    indices.Add((short)(CurrentVertex + (i + 1) % tessellation));
                    indices.Add((short)(CurrentVertex + (i + 2) % tessellation));
                }
                else
                {
                    indices.Add((short)(CurrentVertex));
                    indices.Add((short)(CurrentVertex + (i + 2) % tessellation));
                    indices.Add((short)(CurrentVertex + (i + 1) % tessellation));
                }
            }

            // Create cap vertices.
            for (int i = 0; i < tessellation; i++)
            {
                Vector3 position = GetCircleVector(i, tessellation) * radius +
                                   normal * height;

                vertices.Add(new VertexPositionNormal(position, normal));
            }
        }


#if !MONOGAME
        /// <summary>
        /// Measures how big is a model
        /// - From WinForms Sample
        /// </summary>
        public static void MeasureModel(Model model, out Vector3 modelCenter, out float modelRadius)
        {
            // Look up the absolute bone transforms for this model.
            Matrix[] boneTransforms = new Matrix[model.Bones.Count];

            model.CopyAbsoluteBoneTransformsTo(boneTransforms);

            // Compute an (approximate) model center position by
            // averaging the center of each mesh bounding sphere.
            modelCenter = Vector3.Zero;
            foreach (ModelMesh mesh in model.Meshes)
            {
                BoundingSphere meshBounds = mesh.BoundingSphere;
                Matrix transform = boneTransforms[mesh.ParentBone.Index];
                Vector3 meshCenter = Vector3.Transform(meshBounds.Center, transform);

                modelCenter += meshCenter;
            }

            modelCenter /= model.Meshes.Count;

            // Now we know the center point, we can compute the model radius
            // by examining the radius of each mesh bounding sphere.
            modelRadius = 0;
            foreach (ModelMesh mesh in model.Meshes)
            {
                BoundingSphere meshBounds = mesh.BoundingSphere;
                Matrix transform = boneTransforms[mesh.ParentBone.Index];
                Vector3 meshCenter = Vector3.Transform(meshBounds.Center, transform);

                float transformScale = transform.Forward.Length();

                float meshRadius = (meshCenter - modelCenter).Length() +
                                   (meshBounds.Radius * transformScale);

                modelRadius = Math.Max(modelRadius, meshRadius);
            }
        }
#endif
    }
}
